# -*- coding: utf-8 -*-
# vim: set noet ts=4:
#
# scim-python
#
# Copyright (c) 2007-2008 Huang Peng <shawn.p.huang@gmail.com>
#
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330,
# Boston, MA  02111-1307  USA
#
# $Id: $
#
import scim
from scim import KeyCode
from scim import KeyMask
from scim import Property
import os
import traceback
import locale
import PYParser
import PYSQLiteDB
import curses.ascii as ascii

from gettext import dgettext
_ = lambda (a) : dgettext ("scim-python", a)
N_ = lambda (a) : a

IMEngine = scim.IMEngine
IMEngineFactory = scim.IMEngineFactory


__PINYIN_MAX_LEN__ = 24		# Max length of preedit pinyin

class Engine (IMEngine):
	def __init__ (self, factory, config, encoding, id, pydb):
		IMEngine.__init__ (self, factory, config, encoding, id)
		self._pydb = pydb
		self._config = config
		self._parser = PYParser.PinYinParser ()
		self._lookup_table = scim.LookupTable (9)
		
		# 1 = chinese input mode
		# 0 = english input mode
		self._mode = 1
		self._full_width_letter = [False, False]
		self._full_width_punct = [False, True]

		self.reset ()

		# self._always_show_candidates = config.read ("/IMEngine/Python/AlwaysShowCandidates", False)
		# self._commit_space = config.read ("/IMEngine/Python/CommitSpace", True)
		self._py_parser = PYParser.PinYinParser ()

		self._status_property = Property ("status", "")
		self._letter_property = Property ("full_letter", "")
		self._punct_property = Property ("full_punct", "")

	def init_properties (self):
		props = [self._status_property, self._letter_property, self._punct_property]
		self.register_properties (props)
		self.refresh_properties ()


	def refresh_properties (self):
		self._status_property.label = _("CN") if self._mode == 1 else _("EN")
		self._letter_property.label = "Ａａ" if self._full_width_letter[self._mode] else "Aa"
		self._punct_property.label = "，。" if self._full_width_punct[self._mode] else ", . "
		for prop in [self._status_property, self._letter_property, self._punct_property]:
			self.update_property (prop)
		

	def change_mode (self):
		self._mode = (self._mode + 1) % 2
		self.refresh_properties ()

	def is_input_english (self):
		return self._mode == 0

	def update (self):
		if len (self._candidates) == 0:
			self.hide_lookup_table ()
		else:
			self._lookup_table.clear ()		
			for c in self._candidates:
				self._lookup_table.append_candidate (c)
			self.update_lookup_table (self._lookup_table)
			self.show_lookup_table ()

		if self._preedit_string or self._committed_string:
			self.update_aux_string (self._committed_string + u" " + self._preedit_string)
			self.show_aux_string ()
		else:
			self.hide_aux_string ()
	
	def get_candidates (self, pinyin_list):
		candidates = []
		for i in range (len(pinyin_list), 0, -1):
			candidates += self._pydb.select_words_by_pinyin_list (pinyin_list[:i])
		return candidates


	def append_char (self, char):
		self._inputed_chars.insert (self._cursor, char)
		self._committed_string = u""
		
		try:
			pinyin_list = self._parser.parse (u"".join (self._inputed_chars))
			if len (pinyin_list) >= __PINYIN_MAX_LEN__:
				del self._inputed_chars[self._cursor]
				return True
		except Exception, e:
			del self._inputed_chars[self._cursor]
			return True

		self._cursor += 1

		self._candidates =  self.get_candidates (pinyin_list)
		if self._candidates:
			string = self._candidates [0]
			while len(string) < len (pinyin_list):
				candidates = self.get_candidates (pinyin_list[len (string):])[0]
				if candidates:
					string += candidates[0]
				else:
					break
			if string != self._candidates[0]:
				self._candidates.insert (0, string)

		self._preedit_string = u"'".join (map (str, pinyin_list))
		self.update ()
		return True

	def pop_char (self):
		if not self._inputed_chars:
			return False
		self._inputed_chars.pop ()
		self._committed_string = u""
		self._cursor -= 1

		if not self._inputed_chars:
			self._candidates = []
			self._preedit_string = u""
			self.update ()
			return True

		pinyin_list = self._parser.parse (u"".join (self._inputed_chars))
		self._candidates =  self.get_candidates (pinyin_list)
		string = self._candidates [0]
		while len(string) < len (pinyin_list):
			string += self.get_candidates (pinyin_list[len (string):])[0]

		if string != self._candidates[0]:
			self._candidates.insert (0, string)
		self._preedit_string = u"'".join (map (str, pinyin_list))
		self.update ()

		return True

	def match_hotkey (self, key, code, mask = KeyMask.ReleaseMask):
		if key.code != code or (key.mask & ~ KeyMask.ReleaseMask ) != mask:
			return False

		if not key.mask & KeyMask.ReleaseMask:
			return False

		if self._prev_key and key.code == self._prev_key.code:
			return True

		return False
	
	def process_key_event (self, key):
		result = self.internal_process_key_event (key)
		self._prev_key = key
		return result


	def internal_process_key_event (self, key):
		
		# When CapsLock is lock, we ignore all key events
		if key.mask & KeyMask.CapsLockMask:
			return False
		
		# Match mode switch hotkey
		if self.match_hotkey (key, KeyCode.KEY_Shift_L, KeyMask.ShiftMask):
			self.trigger_property ("status")
			self.reset ()
			return True
		
		# Match full half mode switch hotkey
		if self.match_hotkey (key, KeyCode.KEY_space, KeyMask.ShiftMask):
			self.trigger_property ("full_letter")
			return True

		# we ignore all hotkeys
		if key.mask & (KeyMask.ControlMask + KeyMask.AltMask):
			return False

		# Ignore key release event
		if key.mask & KeyMask.ReleaseMask:
			return True
		
		if self.is_input_english ():
			return self.english_mode_process_key_event (key)
		else:
			return self.chinese_mode_process_key_event (key)

	def convert_to_full_width (self, c):
		if c == u".":
			return u"\u3002"
		elif c == u"\\":
			return u"\u3001"
		elif c == u"^":
			return u"\u2026\u2026"
		elif c == u"_":
			return u"\u2014\u2014"
		elif c == u"$":
			return u"\uffe5"
		elif c == u"\"":
			self._double_quotation_state = not self._double_quotation_state
			if self._double_quotation_state:
				return u"\u201c"
			else:
				return u"\u201d"
		elif c == u"'":
			self._single_quotation_state = not self._single_quotation_state
			if self._single_quotation_state:
				return u"\u2018"
			else:
				return u"\u2019"
 
 		elif c == u"<":
 			if not self.is_input_english ():
 				return u"\u300a"
 		elif c == u">":
 			if not self.is_input_english ():
 				return u"\u300b"
 		
		return scim.unichar_half_to_full (c)
	
	def english_mode_process_key_event (self, key):
		# ignore if key code is not a normal ascii char
		if key.code >= 128:
			return False

		c = chr (key.code)
		if ascii.ispunct (c): # if key code is a punctation
			if self._full_width_punct[self._mode]:
				self.commit_string (self.convert_to_full_width (unicode (c)))
				return True
			else:
				self.commit_string (unicode (c))
				return True
			
		if self._full_width_letter[self._mode]: # if key code is a letter or digit
			self.commit_string (self.convert_to_full_width (unicode (c)))
			return True
		else:
			self.commit_string (unicode (c))
			return True
		
		# should not reach there
		return False

	def chinese_mode_process_key_event (self, key):
		# define a condition half to full width translate functions
		cond_letter_translate = lambda (c): \
			self.convert_to_full_width (c) if self._full_width_letter [self._mode] else c
		cond_punct_translate = lambda (c): \
			self.convert_to_full_width (c) if self._full_width_punct [self._mode] else c

		if key.code in (KeyCode.KEY_KP_Space, KeyCode.KEY_space):
			if self._candidates:
				self.commit_string (self._candidates[0])
			else:
				self.commit_string (cond_letter_translate (u" "))
			return True
		elif key.code in (KeyCode.KEY_Return, KeyCode.KEY_KP_Enter):
			if not self._inputed_chars: # forward Return if inputed chars is empty
				return False
			chars = map (cond_letter_translate, self._inputed_chars)
			commit_string = u"".join (chars)
			self.commit_string (commit_string)
			return True
		elif key.code == KeyCode.KEY_Escape:
			if self._inputed_chars:
				self.reset ()
				return True
			return Fasle
		elif key.code == KeyCode.KEY_BackSpace:
			return self.pop_char ()
		elif key.code >= KeyCode.KEY_1 and key.code <= KeyCode.KEY_9:
			if not self._candidates:
				self.commit_string (cond_letter_translate (unichr (key.code)))
				return True
			if self.commit_candidate (key.code - KeyCode.KEY_1 + self._lookup_table.get_current_page_start ()):
				self.commit_string (self._committed_string)
			return True
		elif key.code == KeyCode.KEY_Page_Up:
			if not self._candidates:
				return False
			self.lookup_table_page_up ()
			return True
		elif key.code == KeyCode.KEY_Page_Down:
			if not self._candidates:
				return False
			self.lookup_table_page_down ()
			return True
		elif (key.code >= KeyCode.KEY_a and key.code <= KeyCode.KEY_z) or \
			(key.code == KeyCode.KEY_apostrophe and self._cursor != 0):
			return self.append_char (unichr (key.code))
		elif self._inputed_chars:
			return True
		elif key.code <= 127:
			c = chr (key.code)
			if ascii.ispunct (c):
				self.commit_string (cond_punct_translate (unichr (key.code)))
			else:
				self.commit_string (cond_letter_translate (unichr (key.code)))
			return True
		
		return False

	def commit_candidate (self, i):
		if i >= len (self._candidates):
			return False
		self._committed_string = self._committed_string + self._candidates[i]
		pinyin_list = self._parser.parse (u"".join (self._inputed_chars))
		if len(self._committed_string) == len(pinyin_list):
			return True
		
		pinyin_list = pinyin_list [len (self._committed_string):]
		self._candidates =  self.get_candidates (pinyin_list)
		string = self._candidates [0]
		while len(string) < len (pinyin_list):
			string += self.get_candidates (pinyin_list[len (string):])[0]

		if string != self._candidates[0]:
			self._candidates.insert (0, string)
		self._preedit_string = u"'".join (map (str, pinyin_list))
		self.update ()

		return False
			
	def commit_string (self, string = None):
		self._candidates = []
		self._cursor = 0
		self._inputed_chars = []
		self._preedit_string = u""
		self._committed_string = u""
		IMEngine.commit_string (self, string)
		self.update ()

	def move_preedit_caret (self, pos):
		IMEngine.move_preedit_caret (self, pos)
	
	def select_candidate (self, index):
		IMEngine.select_candidate (self, index)

	def update_lookup_table_page_size (self, page_size):
		IMEngine.update_lookup_table_page_size (self, page_size)

	def lookup_table_page_up (self):
		if self._lookup_table.page_up ():
			self.update_lookup_table (self._lookup_table)
			return True
		
		IMEngine.lookup_table_page_up (self)
		return True

	def lookup_table_page_down (self):
		if self._lookup_table.page_down ():
			self.update_lookup_table (self._lookup_table)
			return True
		
		IMEngine.lookup_table_page_down (self)
		return True

	def reset (self):
		self._inputed_chars = []
		self._committed_string = u""
		self._preedit_string = u""
		self._candidates = []
		self._cursor = 0
		self._double_quotation_state = False
		self._single_quotation_state = False
		self._prev_key = None
		self.update ()
		IMEngine.reset (self)

	def focus_in (self):
		self.init_properties ()
		IMEngine.focus_in (self)
		self.update ()
	
	def focus_out (self):
		self.reset ()
		IMEngine.focus_out (self)

	def trigger_property (self, property):
		if property == "status":
			self.change_mode ()
		elif property == "full_letter":
			self._full_width_letter [self._mode] = not self._full_width_letter [self._mode]
			self.refresh_properties ()
		elif property == "full_punct":
			self._full_width_punct [self._mode] = not self._full_width_punct [self._mode]
			self.refresh_properties ()
		IMEngine.trigger_property (self, property)

	def process_helper_event (self, helper_uuid, trans):
		IMEngine.process_helper_event (self, helper_uuid, trans)

	def update_client_capabilities (self, cap):
		IMEngine.update_client_capabilities (self, cap)

	def reload_config (self, config):
		# self._always_show_candidates = config.read ("/IMEngine/Python/AlwaysShowCandidates", False)
		# self._commit_space = config.read ("/IMEngine/Python/CommitSpace", True)
		pass

class Factory (IMEngineFactory):
	"""PinYin IM Engine Factory"""
	def __init__ (self, config):
		IMEngineFactory.__init__ (self, config)

		# define factory properties
		self.name 		= _(u"Pin Yin")
		self.uuid 		= "29ab338a-5a27-46b8-96cd-abbe86f17132"
		self.authors	= u"Huang Peng <shawn.p.huang@gmail.com>"
		self.icon_file 	= "/usr/share/scim/icons/scim-python.png"
		self.credits 	= u"GPL"
		self.help		= _(u"Help For PinYin")

		# init factory
		self._config	= config
		self.set_languages ("zh")

		# create pinyin database
		self._pydb = PYSQLiteDB.PYSQLiteDB ()

	def create_instance (self, encoding, id):
		engine =  Engine (self, self._config, encoding, id, self._pydb)
		return engine

	def reload_config (self, config):
		pass
